﻿#region Copyright © 2008 Guillermo Javier Bellmann & José Federico Medrano
/*
* Microsoft Public License (Ms-PL)
*
* This license governs use of the accompanying software.
* If you use the software, you accept this license.
* If you do not accept the license, do not use the software.
*
* 1. Definitions
*
* The terms "reproduce," "reproduction," "derivative works," and "distribution"
* have the same meaning here as under U.S. copyright law.
*
* A "contribution" is the original software, or any additions or changes to the software.
*
* A "contributor" is any person that distributes its contribution under this license.
*
* "Licensed patents" are a contributor's patent claims that read directly on its contribution.
*
* 2. Grant of Rights
*
* (A) Copyright Grant- Subject to the terms of this license, including the license conditions
* and limitations in section 3, each contributor grants you a non-exclusive, worldwide,
* royalty-free copyright license to reproduce its contribution,
* prepare derivative works of its contribution, and distribute its contribution
* or any derivative works that you create.
*
* (B) Patent Grant- Subject to the terms of this license, including the license conditions and
* limitations in section 3, each contributor grants you a non-exclusive, worldwide,
* royalty-free license under its licensed patents to make, have made, use, sell,
* offer for sale, import, and/or otherwise dispose of its contribution in the software or
* derivative works of the contribution in the software.
*
* 3. Conditions and Limitations
*
* (A) No Trademark License- This license does not grant you rights to use any contributors' name,
* logo, or trademarks.
*
* (B) If you bring a patent claim against any contributor over patents that you claim are infringed
* by the software, your patent license from such contributor to the software ends automatically.
*
* (C) If you distribute any portion of the software, you must retain all copyright, patent,
* trademark, and attribution notices that are present in the software.
*
* (D) If you distribute any portion of the software in source code form, you may do so only under
* this license by including a complete copy of this license with your distribution.
* If you distribute any portion of the software in compiled or object code form,
* you may only do so under a license that complies with this license.
*
* (E) The software is licensed "as-is." You bear the risk of using it.
* The contributors give no express warranties, guarantees or conditions.
* You may have additional consumer rights under your local laws which this license cannot change.
* To the extent permitted under your local laws, the contributors exclude the implied warranties
* of merchantability, fitness for a particular purpose and non-infringement.
*/
#endregion

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Reflection;

namespace JujuyASPNET.SourceCodeFormatter
{
    /// <summary>
    /// Provides a base implementation for source code formatters.
    /// </summary>
    /// <remarks>
    /// To use the output of this program you need to include the provided stylesheet
    /// that defines the styling classes generated by the program.
    /// Or put the property InLineStyle to true.
    /// </remarks>
    public abstract class SourceCode
    {
        private byte tabSpaces = 4;
        private bool embedStyleSheet = false;
        private bool alternateColorText = false;
        private bool lineNumbers = false;
        private bool inLineStyle = true;


        #region Properties

        /// <summary>
        /// Gets or sets the tabs width.
        /// </summary>
        /// <value>
        /// The number of space characters to replace a tab character.
        /// Default is <b>4</b>.
        /// </value>
        public byte TabSpaces
        {
            get { return tabSpaces; }
            set { tabSpaces = value; }
        }

        /// <summary>
        /// Enables or disables the embedding of the Stylesheet.
        /// </summary>
        /// <value>
        /// When <b>true</b> the &lt;style&gt; element is included in the HTML output.
        /// Default is <b>false</b>
        /// </value>
        public bool EmbedStyleSheet
        {
            get { return embedStyleSheet; }
            set { embedStyleSheet = value; }
        }

        /// <summary>
        /// Enables or disables the alternate colouring of lines.
        /// </summary>
        /// <value>
        /// When <b>true</b> backgroud color is alternated between lines.
        /// Default is <b>false</b>.
        /// </value>
        public bool AlternateColorText
        {
            get { return alternateColorText; }
            set { alternateColorText = value; }
        }

        /// <summary>
        /// Enables or disables the line numbers of the code.
        /// </summary>
        /// <value>
        /// When <b>true</b> line numbers are inserted on the left of each line.
        /// Default is <b>false</b>.
        /// </value>
        public bool LineNumbers
        {
            get { return lineNumbers; }
            set { lineNumbers = value; }
        }

        /// <summary>
        /// Enables or disables the use of in line styles instead of CSS classes.
        /// </summary>
        /// <value>
        /// When <b>true</b> uses in line style within HTML output.
        /// When <b>false</b> uses the stylesheet classes within the HTML output.
        /// Default is <b>true</b>.
        /// </value>
        public bool InLineStyle
        {
            get { return inLineStyle; }
            set { inLineStyle = value; }
        }

        #endregion


        #region Methods

		/// <summary>
		/// Gets the CSS stylesheet file as a stream.
		/// </summary>
		/// <returns>
        /// A Text <see cref="Stream"/> of the CSS definitions.
        /// </returns>
		public static Stream GetCSSStream()
		{
			return Assembly.GetExecutingAssembly().GetManifestResourceStream(
				"SourceCodeFormatter.SrcFrmt.css");
		}

		/// <summary>
		/// Gets the CSS stylesheet as a string.
		/// </summary>
		/// <returns>
        /// A string containing the CSS definitions.
        /// </returns>
		public static string GetCSSString()
		{
			StreamReader sreader = new StreamReader(GetCSSStream());
			return sreader.ReadToEnd();
		}

        private Regex codeRegex;

        /// <summary>
        /// The regular expression used to capture language tokens.
        /// </summary>
        protected Regex CodeRegex
        {
            get { return codeRegex; }
            set { codeRegex = value; }
        }

        /// <summary>
        /// Called to evaluate the HTML fragment corresponding to each 
        /// matching token in the code.
        /// </summary>
        /// <param name="match">The <see cref="Match"/> resulting from a 
        /// single regular expression match.</param>
        /// <returns>A string containing the HTML code fragment.</returns>
        protected abstract string MatchEval(Match match);

        /// <summary>
        /// Allows formatting a part of the code in a different language,
        /// for example a JavaScript block inside an HTML file.
        /// </summary>
        public string FormatSubCode(string source)
        {
            return FormatCode(source, false, false, false, true, inLineStyle);
        }

        /// <overloads>Transform source code to HTML 4.01.</overloads>
        /// 
        /// <summary>
        /// Transforms a source code stream to HTML 4.01.
        /// </summary>
        /// <param name="source">Source code stream.</param>
        /// <returns>A string containing the HTML formatted code.</returns>
        public string FormatCode(Stream source)
        {
            StreamReader reader = new StreamReader(source);
            string s = reader.ReadToEnd();
            reader.Close();
            return FormatCode(s, lineNumbers, alternateColorText, embedStyleSheet, false, inLineStyle);
        }

        /// <summary>
        /// Transforms a source code string to HTML 4.01.
        /// </summary>
        /// <returns>A string containing the HTML formatted code.</returns>
        public string FormatCode(string source)
        {
            return FormatCode(source, lineNumbers, alternateColorText, embedStyleSheet, false, inLineStyle);
        }

        //does the formatting job
        private string FormatCode(string source, bool lineNumbers,
            bool alternate, bool embedStyleSheet, bool subCode, bool inLineStyles)
        {
            //replace special characters
            StringBuilder sb = new StringBuilder(source);

            if (!subCode)
            {
                sb.Replace("&", "&amp;");
                sb.Replace("<", "&lt;");
                sb.Replace(">", "&gt;");
                sb.Replace("\t", string.Empty.PadRight(tabSpaces));
            }

            //color the code
            source = codeRegex.Replace(sb.ToString(), new MatchEvaluator(this.MatchEval));

            sb = new StringBuilder();

            if (embedStyleSheet)
            {
                sb.Append("<style type=\"text/css\">\n");
                sb.Append(GetCSSString());
                sb.Append("</style>\n");
            }

            if (lineNumbers || alternate) //we have to process the code line by line
            {
                if (!subCode)
                    sb.Append("<div class=\"srcformat\">\n");
                StringReader reader = new StringReader(source);
                int i = 0;
                string spaces = "    ";
                int order;
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    i++;
                    if (alternate && ((i % 2) == 1))
                    {
                        sb.Append("<pre class=\"alt\">");
                    }
                    else
                    {
                        sb.Append("<pre>");
                    }

                    if (lineNumbers)
                    {
                        order = (int)Math.Log10(i);
                        sb.Append("<span class=\"lnum\">"
                            + spaces.Substring(0, 3 - order) + i.ToString()
                            + ":  </span>");
                    }

                    if (line.Length == 0)
                        sb.Append("&nbsp;");
                    else
                        sb.Append(line);
                    sb.Append("</pre>\n");
                }
                reader.Close();
                if (!subCode)
                    sb.Append("</div>");
            }
            else
            {
                //have to use a <pre> because IE below ver 6 does not understand 
                //the "white-space: pre" CSS value
                if (!subCode)
                    sb.Append("<pre class=\"srcformat\">\n");
                sb.Append(source);
                if (!subCode)
                    sb.Append("</pre>");
            }
            //This is necessary to when you see the html from a different place than the original
            //page, like Google Reader, and you don't have the stylesheet.
            if (inLineStyles)
            {
                sb = sb.Replace("class=\"srcformat\"", "style=\"font-size:12px;color:black;font-family:Consolas;background-color:#FFFFFF;line-height:normal;\"");
                sb = sb.Replace("class=\"rem\"", "style=\"color:#008000;\"");
                sb = sb.Replace("class=\"kwrd\"", "style=\"color:#0000FF;\"");
                sb = sb.Replace("class=\"csstr\"", "style=\"color:#A31515;\"");
                sb = sb.Replace("class=\"str\"", "style=\"color:#006080;\"");
                sb = sb.Replace("class=\"cspreproc\"", "style=\"color:#0000FF;\"");
                sb = sb.Replace("class=\"preproc\"", "style=\"color:#CC6633;\"");
                sb = sb.Replace("class=\"asp\"", "style=\"background-color:#A31515;\"");
                sb = sb.Replace("class=\"html\"", "style=\"color:#A31515;\"");
                sb = sb.Replace("class=\"attr\"", "style=\"color:#FF0000;\"");
                sb = sb.Replace("class=\"alt\"", "style=\"background-color:#F4F4F4;width:100%;margin:0;\"");
                sb = sb.Replace("class=\"lnum\"", "style=\"color:#2B91AF;\"");
                sb = sb.Replace("<pre>", "<pre style=\"margin:0px\">");
            }
            return sb.ToString();
        }
        #endregion

    }
}
